home *** CD-ROM | disk | FTP | other *** search
/ SGI Freeware 2001 May / SGI Freeware 2001 May - Disc 1.iso / dist / fw_ispell.idb / usr / freeware / bin / munchlist.z / munchlist (.txt)
Microsoft Windows Help File Content  |  2000-01-25  |  26KB  |  613 lines

  1. : Use /bin/sh
  2. # $Id: munchlist.X,v 1.53 1995/01/08 23:23:36 geoff Exp $
  3. # Copyright 1987, 1988, 1989, 1992, 1993, Geoff Kuenning, Granada Hills, CA
  4. # All rights reserved.
  5. # Redistribution and use in source and binary forms, with or without
  6. # modification, are permitted provided that the following conditions
  7. # are met:
  8. # 1. Redistributions of source code must retain the above copyright
  9. #    notice, this list of conditions and the following disclaimer.
  10. # 2. Redistributions in binary form must reproduce the above copyright
  11. #    notice, this list of conditions and the following disclaimer in the
  12. #    documentation and/or other materials provided with the distribution.
  13. # 3. All modifications to the source code must be clearly marked as
  14. #    such.  Binary redistributions based on modified source code
  15. #    must be clearly marked as modified versions in the documentation
  16. #    and/or other materials provided with the distribution.
  17. # 4. All advertising materials mentioning features or use of this software
  18. #    must display the following acknowledgment:
  19. #      This product includes software developed by Geoff Kuenning and
  20. #      other unpaid contributors.
  21. # 5. The name of Geoff Kuenning may not be used to endorse or promote
  22. #    products derived from this software without specific prior
  23. #    written permission.
  24. # THIS SOFTWARE IS PROVIDED BY GEOFF KUENNING AND CONTRIBUTORS ``AS IS'' AND
  25. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  26. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  27. # ARE DISCLAIMED.  IN NO EVENT SHALL GEOFF KUENNING OR CONTRIBUTORS BE LIABLE
  28. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  29. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  30. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  31. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  32. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  33. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  34. # SUCH DAMAGE.
  35. #    Given a list of words for ispell, generate a reduced list
  36. #    in which all possible affixes have been collapsed.  The reduced
  37. #    list will match the same list as the original.
  38. #    Usage:
  39. #    munchlist [-l lang] [-c lang] [-s hashfile] [-D] [-w chars] [-v] \
  40. #      [file] ...
  41. #    Options:
  42. #    -l lang    Specifies the language table to be used.  The default
  43. #        is "$LIBDIR/english.aff".
  44. #    -c lang    Specifies "conversion" language table.  If this option is
  45. #        given, the input file(s) will be assumed to be described by
  46. #        this table, rather than the table given in the -l option.
  47. #        This may be used to convert between incompatible language
  48. #        tables.  (When in doubt, use this option -- it doesn't
  49. #        hurt, and it may save you from creating a dictionary that has
  50. #        illegal words in it).  The default is no conversion.
  51. #    -T suff Specifies that the source word lists are in the format
  52. #        of a "suff"-suffixed file, rather than in the
  53. #        canonical form.  For example, "-T tex" specifies that
  54. #        string characters in the word lists are in TeX format.
  55. #        The string character conversions are taken from the language
  56. #        table specified by the "-l" switch.
  57. #    -s    Remove any words that are already covered by the
  58. #        dictionary in 'hashfile'.  The words will be removed
  59. #        only if all affixes are covered.  This option should not be
  60. #        specified when the main dictionary is being munched.
  61. #        'Hashfile' must have been created with the language
  62. #        table given in the -l option, but this is not checked.
  63. #    -D    Leave temporary files for debugging purposes
  64. #    -w    Passed on to ispell (specify chars that are part of a word)
  65. #        Unfortunately, special characters must be quoted twice
  66. #        rather than once when invoking this script.  Also, since
  67. #        buildhash doesn't accept this option, the final ispell -l
  68. #        step ignores it, making it somewhat less than useful.
  69. #    -v    Report progress to stderr.
  70. #    The given input files are merged, then processed by 'ispell -c'
  71. #    to generate possible affix lists;  these are then combined
  72. #    and reduced.  The final result is written to standard output.
  73. #    For portability to older systems, I have avoided getopt.
  74. #        Geoff Kuenning
  75. #        2/28/87
  76. # $Log: munchlist.X,v $
  77. # Revision 1.53  1995/01/08  23:23:36  geoff
  78. # Support variable hashfile suffixes for DOS purposes.
  79. # Revision 1.52  1994/12/27  23:08:46  geoff
  80. # Dynamically determine how to pass backslashes to 'tr' so that it'll
  81. # work on any machine.  Define LC_CTYPE to work around yet more
  82. # internationalized sort programs.  Work around a bug in GNU uniq that
  83. # uses the wrong separator between counts and duplicated lines.
  84. # Revision 1.51  1994/11/21  07:02:54  geoff
  85. # Correctly quote the arguments to 'tr' when detecting systems with
  86. # unsigned sorts.  Be sure to provide a zero exit status on all systems,
  87. # even if MUNCHDEBUG is not set.
  88. # Revision 1.50  1994/10/25  05:46:05  geoff
  89. # Export values for LANG and LOCALE in an attempt to override some
  90. # stupidly-internationalized sort programs.
  91. # Revision 1.49  1994/10/04  03:51:30  geoff
  92. # Add the MUNCHMAIL feature.  If the MUNCHMAIL environment variable is
  93. # set to an email address, debugging information about the munchlist run
  94. # will automatically be collected and mailed to that address.
  95. # Revision 1.48  1994/05/17  06:32:06  geoff
  96. # Don't look for affix tables in LIBDIR if the name contains a slash
  97. # Revision 1.47  1994/04/27  02:50:48  geoff
  98. # Fix some cosmetic flaws in the verbose-mode messages.
  99. # Revision 1.46  1994/01/25  07:11:59  geoff
  100. # Get rid of all old RCS log lines in preparation for the 3.1 release.
  101. if [ "X$MUNCHMAIL" != X ]
  102.     exec 2> /tmp/munchlist.mail
  103.     echo "munchlist $*" 1>&2
  104.     set -vx
  105. LIBDIR=/usr/freeware/lib/ispell
  106. TDIR=${TMPDIR-/usr/tmp}
  107. TMP=${TDIR}/munch$$
  108. SORTTMP="-T ${TDIR}"            # !!SORTTMP!!
  109. if [ -r ./icombine ]
  110.     COMBINE=./icombine
  111.     COMBINE=icombine
  112. if [ -r ./ijoin ]
  113.     JOIN=./ijoin
  114.     JOIN=ijoin
  115. # The following is necessary so that some internationalized versions of
  116. # sort(1) don't confuse things by sorting into a nonstandard order.
  117. LANG=C
  118. LOCALE=C
  119. LC_CTYPE=C
  120. export LANG LOCALE LC_CTYPE
  121. debug=no
  122. dictopt=
  123. langtabs=${LIBDIR}/english.aff
  124. convtabs=
  125. strip=no
  126. icflags=
  127. verbose=false
  128. # The following value of "wchars" is necessary to prevent ispell from
  129. # receiving a null argument if -w is not specified.  As long as "A" is
  130. # a member of the existing character set, ispell will ignore the argument.
  131. wchars=-wA
  132. while [ $# != 0 ]
  133.     case "$1" in
  134.         case "$2" in
  135.         */*)
  136.             langtabs=$2
  137.             ;;
  138.             if [ -r "$2" ]
  139.             then
  140.             langtabs="$2"
  141.             else
  142.             langtabs="${LIBDIR}/$2"
  143.             fi
  144.             ;;
  145.         esac
  146.         if [ ! -r "$langtabs" ]
  147.         then
  148.         echo "Can't open language table '$2'" 1>&2
  149.         exit 1
  150.         fi
  151.         shift
  152.         ;;
  153.         if [ -r "$2" ]
  154.         then
  155.         convtabs="$2"
  156.         elif [ -r "${LIBDIR}/$2" ]
  157.         then
  158.         convtabs="${LIBDIR}/$2"
  159.         else
  160.         echo "Can't open conversion language table '$2'" 1>&2
  161.         exit 1
  162.         fi
  163.         shift
  164.         ;;
  165.         dictopt="-d $2"
  166.         strip=yes
  167.         shift
  168.         ;;
  169.         debug=yes
  170.         ;;
  171.         icflags="-T $2"
  172.         shift
  173.         ;;
  174.         verbose=true
  175.         ;;
  176.         wchars="-w$2"
  177.         shift
  178.         ;;
  179.         shift
  180.         break
  181.         ;;
  182.         break
  183.         ;;
  184.         echo 'Usage: munchlist [-l lang] [-c lang] [-T suff] [-s hashfile] [-D] [-w chars] [-v] [file] ...' \
  185.           1>&2
  186.         exit 2
  187.         ;;
  188.         break
  189.         ;;
  190.     esac
  191.     shift
  192. if [ "X$MUNCHMAIL" != X ]
  193.     verbose=true
  194.     debug=yes
  195. trap "/bin/rm -f ${TMP}*; exit 1" 1 2 13 15
  196. # Names of temporary files.  This is just to make the code a little easier
  197. # to read.
  198. EXPANDEDINPUT=${TMP}a
  199. STRIPPEDINPUT=${TMP}b
  200. CRUNCHEDINPUT=${TMP}c
  201. PRODUCTLIST=${TMP}d
  202. EXPANDEDPAIRS=${TMP}e
  203. LEGALFLAGLIST=${TMP}f
  204. JOINEDPAIRS=${TMP}g
  205. MINIMALAFFIXES=${TMP}h
  206. CROSSROOTS=${TMP}i
  207. CROSSEXPANDED=${TMP}j
  208. CROSSPAIRS=${TMP}k
  209. CROSSILLEGAL=${TMP}l
  210. ILLEGALCOMBOS=${TMP}m
  211. FAKEDICT=${TMP}n
  212. # Ispell insists that hash files have a ".hash" suffix
  213. FAKEHASH=${TMP}o.hash
  214. AWKSCRIPT=${TMP}p
  215. if [ "$debug" = yes ]
  216.     touch $EXPANDEDINPUT $STRIPPEDINPUT $CRUNCHEDINPUT $PRODUCTLIST \
  217.       $EXPANDEDPAIRS $LEGALFLAGLIST $JOINEDPAIRS $MINIMALAFFIXES \
  218.       $CROSSROOTS $CROSSEXPANDED $CROSSPAIRS $CROSSILLEGAL $ILLEGALCOMBOS \
  219.       $FAKEDICT $FAKEHASH $AWKSCRIPT
  220.     rm -f ${TDIR}/EXPANDEDINPUT ${TDIR}/STRIPPEDINPUT ${TDIR}/CRUNCHEDINPUT \
  221.       ${TDIR}/PRODUCTLIST ${TDIR}/EXPANDEDPAIRS ${TDIR}/LEGALFLAGLIST \
  222.       ${TDIR}/JOINEDPAIRS ${TDIR}/MINIMALAFFIXES ${TDIR}/CROSSROOTS \
  223.       ${TDIR}/CROSSEXPANDED ${TDIR}/CROSSPAIRS ${TDIR}/CROSSILLEGAL \
  224.       ${TDIR}/ILLEGALCOMBOS ${TDIR}/FAKEDICT ${TDIR}/FAKEHASH.hash \
  225.       ${TDIR}/AWKSCRIPT ${TDIR}/CROSSROOTS.[0-9]* ${TDIR}/CROSSEXP.[0-9]* \
  226.       ${TDIR}/CROSSPAIRS.[0-9]* ${TDIR}/CROSSILLEGAL.[0-9]*
  227.     ln $EXPANDEDINPUT ${TDIR}/EXPANDEDINPUT
  228.     ln $STRIPPEDINPUT ${TDIR}/STRIPPEDINPUT
  229.     ln $CRUNCHEDINPUT ${TDIR}/CRUNCHEDINPUT
  230.     ln $PRODUCTLIST ${TDIR}/PRODUCTLIST
  231.     ln $EXPANDEDPAIRS ${TDIR}/EXPANDEDPAIRS
  232.     ln $LEGALFLAGLIST ${TDIR}/LEGALFLAGLIST
  233.     ln $JOINEDPAIRS ${TDIR}/JOINEDPAIRS
  234.     ln $MINIMALAFFIXES ${TDIR}/MINIMALAFFIXES
  235.     ln $CROSSROOTS ${TDIR}/CROSSROOTS
  236.     ln $CROSSEXPANDED ${TDIR}/CROSSEXPANDED
  237.     ln $CROSSPAIRS ${TDIR}/CROSSPAIRS
  238.     ln $CROSSILLEGAL ${TDIR}/CROSSILLEGAL
  239.     ln $ILLEGALCOMBOS ${TDIR}/ILLEGALCOMBOS
  240.     ln $FAKEDICT ${TDIR}/FAKEDICT
  241.     ln $FAKEHASH ${TDIR}/FAKEHASH.hash
  242.     ln $AWKSCRIPT ${TDIR}/AWKSCRIPT
  243. # Create a dummy dictionary to hold a compiled copy of the language
  244. # table.  Initially, it holds the conversion table, if it exists.
  245. case "X$convtabs" in
  246.     X)
  247.     convtabs="$langtabs"
  248. echo 'QQQQQQQQ' > $FAKEDICT
  249. buildhash -s $FAKEDICT $convtabs $FAKEHASH \
  250.   ||  (echo "Couldn't create fake hash file" 1>&2; /bin/rm -f ${TMP}*; exit 1) \
  251.   ||  exit 1
  252. # Figure out how 'sort' sorts signed fields, for arguments to ijoin.
  253. # This is a little bit of a tricky pipe, but the result is that SIGNED
  254. # is set to "-s" if characters with the top bit set sort before those
  255. # without, and "-u" if the reverse is true.  How does it work?  The
  256. # first "tr" step generates two lines, one containing "-u", the other
  257. # with the same but with the high-order bit set.  The second "tr"
  258. # changesthe high-bit "-u" back to "-s".  If the high-bit "-u" was
  259. # sorted first, the sed step will select "-s" for SIGNED; otherwise
  260. # it'll pick "-u".  We have to be careful about backslash quoting
  261. # conventions, because some systems differ.
  262. backslash=\\
  263. for i in 0 1 2 3
  264.     if [ `echo a | tr "${backslash}141" b` = b ]
  265.     then
  266.     break
  267.     fi
  268.     backslash="$backslash$backslash"
  269. SIGNED=`echo '-s
  270. -u' | tr s "${backslash}365" | sort | tr "${backslash}365" s | sed 1q`
  271. # Collect all the input and expand all the affix options (ispell -e),
  272. # and preserve (sorted) for later joining in EXPANDEDINPUT.  The icombine
  273. # step is to make sure that unneeded capitalizations (e.g., Farmer and farmer)
  274. # are weeded out.  The first sort must be folded for icombine;  the second
  275. # must be unfolded for join.
  276. $verbose  &&  echo "Collecting input." 1>&2
  277. if [ $# -eq 0 ]
  278.     ispell "$wchars" -e1 -d $FAKEHASH -p /dev/null | tr " " '
  279.     cat "$@" | ispell "$wchars" -e1 -d $FAKEHASH -p /dev/null | tr " " '
  280.   | sort $SORTTMP -u +0f -1 +0 \
  281.   | $COMBINE $icflags $langtabs \
  282.   | sort $SORTTMP -u > $EXPANDEDINPUT
  283. # If a conversion table existed, recreate the fake hash file with the
  284. # "real" language table.
  285. case "$convtabs" in
  286.     $langtabs)
  287.     *)
  288.     buildhash -s $FAKEDICT $langtabs $FAKEHASH \
  289.       ||  (echo "Couldn't create fake hash file" 1>&2; \
  290.         /bin/rm -f ${TMP}*; exit 1) \
  291.       ||  exit 1
  292. /bin/rm -f ${FAKEDICT}*
  293. # If the -s (strip) option was specified, remove all
  294. # expanded words that are covered by the dictionary.  This produces
  295. # the final list of expanded words that this dictionary must cover.
  296. # Leave the list in STRIPPEDINPUT.
  297. if [ "X$strip" = "Xno" ]
  298.     rm -f $STRIPPEDINPUT
  299.     ln $EXPANDEDINPUT $STRIPPEDINPUT
  300.     if [ "$debug" = yes ]
  301.     then
  302.     rm -f ${TDIR}/STRIPPEDINPUT
  303.     ln $STRIPPEDINPUT ${TDIR}/STRIPPEDINPUT
  304.     fi
  305.     $verbose  &&  echo "Stripping words already in the dictionary." 1>&2
  306.     ispell "$wchars" -l $dictopt -p /dev/null < $EXPANDEDINPUT \
  307.       > $STRIPPEDINPUT
  308. # Figure out what the flag-marking character is.
  309. $verbose  &&  echo "Finding flag marker." 1>&2
  310. flagmarker=`ispell -D -d $FAKEHASH \
  311.   | sed -n '/^flagmarker/s/flagmarker //p'`
  312. case "$flagmarker" in
  313.     \\*)
  314.     flagmarker=`expr "$flagmarker" : '.\(.\)'`
  315. esac    
  316. # Munch the input to generate roots and affixes (ispell -c).  We are
  317. # only interested in words that have at least one affix (egrep $flagmarker);
  318. # the next step will pick up the rest.  Some of the roots are illegal.  We
  319. # use join to restrict the output to those root words that are found
  320. # in the original dictionary.
  321. $verbose  &&  echo "Generating roots and affixes." 1>&2
  322. ispell "$wchars" -c -W0 -d $FAKEHASH -p /dev/null < $STRIPPEDINPUT \
  323.   | tr " " '
  324.   | egrep "$flagmarker" | sort $SORTTMP -u "-t$flagmarker" +0 -1 +1 \
  325.   | $JOIN $SIGNED "-t$flagmarker" - $EXPANDEDINPUT > $CRUNCHEDINPUT
  326. # We now have a list of legal roots, and of affixes that apply to the
  327. # root words.  However, it is possible for some affix flags to generate more
  328. # than one output word.  For example, with the flag table entry
  329. #    flag R:    . > ER
  330. #        . > ERS
  331. # the input "BOTHER" will generate an entry "BOTH/R" in CRUNCHEDINPUT.  But
  332. # this will accept "BOTHER" and "BOTHERS" in the dictionary, which is
  333. # wrong (in this case, though it's good English).
  334. # To cure this problem, we first have to know which flags generate which
  335. # expansions.  We use ispell -e3 to expand the flags (the second e causes
  336. # the root and flag to be included in the output), and get pairs
  337. # suitable for joining.  In the example above, we would get
  338. #    BOTH/R BOTHER
  339. #    BOTH/R BOTHERS
  340. # We save this in EXPANDEDPAIRS for the next step.
  341. $verbose  &&  echo 'Expanding dictionary into EXPANDEDPAIRS.' 1>&2
  342. ispell "$wchars" -e3 -d $FAKEHASH -p /dev/null < $CRUNCHEDINPUT \
  343.   | sort $SORTTMP +1 > $EXPANDEDPAIRS
  344. # Now we want to extract the lines in EXPANDEDPAIRS in which the second field
  345. # is *not* listed in the original dictionary EXPANDEDINPUT;  these illegal
  346. # lines contain the flags we cannot include without accepting illegal words.
  347. # It is somewhat easier to extract those which actually are listed (with
  348. # join), and then use comm to strip these from EXPANDEDPAIRS to get the
  349. # illegal expansions, together with the flags that generate them (we must
  350. # re-sort EXPANDEDPAIRS before running comm).  Sed
  351. # gets rid of the expansion and uniq gets rid of duplicates.  Comm then
  352. # selects the remainder of the list from CRUNCHEDINPUT and puts it in
  353. # LEGALFLAGLIST.  The final step is to use a sort and icombine to put
  354. # the list into a one-entry-per-root format.
  355. # BTW, I thought of using cut for the sed step (on systems that have it),
  356. # but it turns out that sed is faster!
  357. $JOIN -j1 2 -o 1.1 1.2 $SIGNED $EXPANDEDPAIRS $EXPANDEDINPUT \
  358.   | sort $SORTTMP -u > $JOINEDPAIRS
  359. sort $SORTTMP -o $EXPANDEDPAIRS $EXPANDEDPAIRS
  360. sort $SORTTMP -o $CRUNCHEDINPUT $CRUNCHEDINPUT
  361. $verbose  &&  echo 'Creating list of legal roots/flags.' 1>&2
  362. comm -13 $JOINEDPAIRS $EXPANDEDPAIRS \
  363.   | (sed -e 's; .*$;;' ; /bin/rm -f $JOINEDPAIRS $EXPANDEDPAIRS) \
  364.   | uniq \
  365.   | (comm -13 - $CRUNCHEDINPUT ; /bin/rm -f $CRUNCHEDINPUT) \
  366.   | sort $SORTTMP -u "-t$flagmarker" +0f -1 +0 \
  367.   | $COMBINE $langtabs > $LEGALFLAGLIST
  368. # LEGALFLAGLIST now contains root/flag combinations that, when expanded,
  369. # produce only words from EXPANDEDPAIRS.  However, there is still a
  370. # problem if the language tables have any cross-product flags.  A legal
  371. # root may appear in LEGALFLAGLIST with two flags that participate
  372. # in cross-products.  When such a dictionary entry is expanded,
  373. # the cross-products will generate some extra words that may not
  374. # be in EXPANDEDPAIRS.  We need to remove these from LEGALFLAGLIST.
  375. # The first step is to collect the names of the flags that participate
  376. # in cross-products.  Ispell will dump the language tables for us, and
  377. # sed is a pretty handy way to strip out extra information.  We use
  378. # uniq -c and a numerical sort to put the flags in approximate order of how
  379. # "productive" they are (in terms of how likely they are to generate a lot
  380. # of output words).  The least-productive flags are given last and will
  381. # be removed first.
  382. $verbose \
  383.   &&  echo 'Creating list of flags that participate in cross-products.' 1>&2
  384. ispell -D -d $FAKEHASH \
  385.   | sed -n '1,$s/:.*$//
  386.     /^flagmarker/d
  387.     /^prefixes/,/^suffixes/s/^  flag \*/p /p
  388.     /^suffixes/,$s/^  flag \*/s /p' \
  389.   | sort $SORTTMP \
  390.   | uniq -c \
  391.   | tr '    ' ' ' \
  392.   | sort $SORTTMP +0rn -1 +2 > $PRODUCTLIST
  393. if [ `egrep ' p ' $PRODUCTLIST | wc -l` -gt 0 \
  394.   -a `egrep ' s ' $PRODUCTLIST | wc -l` -gt 0 ]
  395.     #
  396.     # The language tables allow cross products.  See if LEGALFLAGLIST has
  397.     # any roots with multiple cross-product flags.  Put them in CROSSROOTS.
  398.     #
  399.     $verbose  &&  echo 'Finding prefix and suffix flags.' 1>&2
  400.     preflags=`sed -n 's/^[ 0-9]*p //p' $PRODUCTLIST | tr -d '
  401.     sufflags=`sed -n 's/^[ 0-9]*s //p' $PRODUCTLIST | tr -d '
  402.     egrep "$flagmarker.*[$preflags].*[$sufflags]|$flagmarker.*[$sufflags].*[$preflags]" \
  403.       $LEGALFLAGLIST \
  404.       > $CROSSROOTS
  405.     #
  406.     # We will need an awk script;  it's so big that it core-dumps my shell
  407.     # under certain conditions.  The rationale behind the script is commented
  408.     # where the script is used.  Note that you may want to change this
  409.     # script for languages other than English.
  410.     #
  411.     case "$flagmarker" in
  412.         sedchar=:
  413.         ;;
  414.         sedchar=/
  415.         ;;
  416.     esac
  417.     $verbose  &&  echo 'Creating awk script.' 1>&2
  418.     sed -e "s/PREFLAGS/$preflags/" -e "s/SUFFLAGS/$sufflags/" \
  419.       -e "s;ILLEGALCOMBOS;$ILLEGALCOMBOS;" \
  420.       -e "s${sedchar}FLAGMARKER${sedchar}$flagmarker${sedchar}" \
  421.       > $AWKSCRIPT << 'ENDOFAWKSCRIPT'
  422.     BEGIN \
  423.         {
  424.         preflags = "PREFLAGS"
  425.         sufflags = "SUFFLAGS"
  426.         illegalcombos = "ILLEGALCOMBOS"
  427.         flagmarker = "FLAGMARKER"
  428.         pflaglen = length (preflags)
  429.         for (i = 1;  i <= pflaglen;  i++)
  430.         pflags[i] = substr (preflags, i, 1);
  431.         sflaglen = length (sufflags)
  432.         for (i = 1;  i <= sflaglen;  i++)
  433.         sflags[i] = substr (sufflags, i, 1);
  434.         }
  435.         {
  436.         len = length ($2)
  437.         pnew2 = ""
  438.         snew2 = ""
  439.         pbad = ""
  440.         sbad = ""
  441.         sufs = 0
  442.         pres = 0
  443.         for (i = 1;  i <= len;  i++)
  444.         curflag = substr ($2, i, 1)
  445.         for (j = 1;  j <= pflaglen;  j++)
  446.             {
  447.             if (pflags[j] == curflag)
  448.             pres++
  449.             pnew2 = substr ($2, 1, i - 1) substr ($2, i + 1)
  450.             pbad = curflag
  451.             }
  452.         for (j = 1;  j <= sflaglen;  j++)
  453.             {
  454.             if (sflags[j] == curflag)
  455.             sufs++
  456.             snew2 = substr ($2, 1, i - 1) substr ($2, i + 1)
  457.             sbad = curflag
  458.             }
  459.         if (pres == 1)
  460.         print $1 flagmarker pnew2
  461.         print $1 flagmarker pbad >> illegalcombos
  462.         else if (sufs == 1)
  463.         print $1 flagmarker snew2
  464.         print $1 flagmarker sbad >> illegalcombos
  465.         else if (pres > 0)
  466.         print $1 flagmarker pnew2
  467.         print $1 flagmarker pbad >> illegalcombos
  468.         else
  469.         print $1 flagmarker snew2
  470.         print $1 flagmarker sbad >> illegalcombos
  471.         }
  472. ENDOFAWKSCRIPT
  473.     : > $ILLEGALCOMBOS
  474.     dbnum=0
  475.     while [ -s $CROSSROOTS ]
  476.     do
  477.     # CROSSROOTS contains the roots whose cross-product expansions
  478.     # might be illegal.  We now need to locate the actual illegal ones.
  479.     # We do this in much the same way we created LEGALFLAGLIST from
  480.     # CRUNCHEDINPUT.  First we make CROSSEXPANDED, which is analogous
  481.     # to EXPANDEDPAIRS.
  482.     $verbose  &&  echo "Creating cross expansions (pass $dbnum)." 1>&2
  483.     ispell "$wchars" -e3 -d $FAKEHASH -p /dev/null < $CROSSROOTS \
  484.       | sort $SORTTMP +1 > $CROSSEXPANDED
  485.     # Now we join CROSSEXPANDED against EXPANDEDINPUT to produce
  486.     # CROSSPAIRS, and then comm that against CROSSEXPANDED to
  487.     # get CROSSILLEGAL, the list of illegal cross-product flag
  488.     # combinations.
  489.     $JOIN -j1 2 -o 1.1 1.2 $SIGNED $CROSSEXPANDED $EXPANDEDINPUT \
  490.       | sort $SORTTMP -u > $CROSSPAIRS
  491.     sort $SORTTMP -u -o $CROSSEXPANDED $CROSSEXPANDED
  492.     $verbose \
  493.       &&  echo "Finding illegal cross expansions (pass $dbnum)." 1>&2
  494.     comm -13 $CROSSPAIRS $CROSSEXPANDED \
  495.       | sed -e 's; .*$;;' \
  496.       | uniq > $CROSSILLEGAL
  497.     if [ "$debug" = yes ]
  498.     then
  499.         mv $CROSSROOTS $TDIR/CROSSROOTS.$dbnum
  500.         ln $CROSSEXPANDED $TDIR/CROSSEXP.$dbnum
  501.         ln $CROSSPAIRS $TDIR/CROSSPAIRS.$dbnum
  502.         ln $CROSSILLEGAL $TDIR/CROSSILLEGAL.$dbnum
  503.     # Now it is time to try to clear up the illegalities.  For 
  504.     # each word in the illegal list, remove one of the cross-product
  505.     # flags.  The flag chosen is selected in an attempt to cure the
  506.     # problem quickly, as follows:  (1) if there is only one suffix
  507.     # flag or only one prefix flag, we remove that.  (2) If there is
  508.     # a prefix flag, we remove the "least desirable" (according to
  509.     # the order of preflags). (This may be pro-English prejudice,
  510.     # and you might want to change this if your language is prefix-heavy).
  511.     # (3) Otherwise we remove the least-desirable suffix flag
  512.     # The output of the awk script becomes the new CROSSROOTS.  In
  513.     # addition, we add the rejected flags to ILLEGALCOMBOS (this is done
  514.     # inside the awk script) so they can be removed from LEGALFLAGLIST
  515.     # later.
  516.     awk "-F$flagmarker" -f $AWKSCRIPT $CROSSILLEGAL > $CROSSROOTS
  517.     if [ "$debug" = yes ]
  518.     then
  519.         /bin/rm -f $CROSSEXPANDED $CROSSPAIRS $CROSSILLEGAL
  520.     dbnum=`expr $dbnum + 1`
  521.     done
  522.     /bin/rm -f $CROSSEXPANDED $CROSSPAIRS $CROSSILLEGAL $AWKSCRIPT
  523.     #
  524.     # Now we have, in ILLEGALCOMBOS, a list of root/flag combinations
  525.     # that must be removed from LEGALFLAGLIST to get the final list
  526.     # of truly legal flags.  ILLEGALCOMBOS has one flag per line, so
  527.     # by turning LEGALFLAGLIST into this form (sed), it's an
  528.     # easy task for comm.  We have to recombine flags again after the
  529.     # extraction, to get all flags for a given root on the same line so that
  530.     # cross-products will come out right.
  531.     #
  532.     if [ -s $ILLEGALCOMBOS ]
  533.     then
  534.     sort $SORTTMP -u -o $ILLEGALCOMBOS $ILLEGALCOMBOS
  535.     $verbose  &&  echo 'Finding roots of cross expansions.' 1>&2
  536.     sort $SORTTMP $LEGALFLAGLIST \
  537.       | sed '/\/../{
  538.           s;^\(.*\)/\(.\)\(.*\);\1/\2\
  539. \1/\3;
  540.           P
  541.           D
  542.           }' \
  543.       | comm -23 - $ILLEGALCOMBOS \
  544.       | sort $SORTTMP -u "-t$flagmarker" +0f -1 +0 \
  545.       | $COMBINE $langtabs > $CROSSROOTS
  546.     mv $CROSSROOTS $LEGALFLAGLIST
  547.     if [ "$debug" = yes ]
  548.     then
  549.         rm -f ${TDIR}/LEGALFLAGLIST1
  550.         ln $LEGALFLAGLIST ${TDIR}/LEGALFLAGLIST1
  551.     fi
  552. /bin/rm -f $PRODUCTLIST $CROSSROOTS $ILLEGALCOMBOS $EXPANDEDINPUT
  553. # We now have (in LEGALFLAGLIST) a list of roots and flags which will
  554. # accept words taken from EXPANDEDINPUT and no others (though some of
  555. # EXPANDEDINPUT is not covered by this list).  However, many of the
  556. # expanded words can be generated in more than one way.  For example,
  557. # "bather" can be generated from "bath/R" and "bathe/R".  This wastes
  558. # unnecessary space in the raw dictionary and, in some cases, in the
  559. # hash file as well.  The solution is to list the various ways of
  560. # getting a given word and choose exactly one.  All other things being
  561. # equal, we want to choose the one with the highest expansion length
  562. # to root length ratio.  The ispell -e4 option takes care of this by
  563. # providing us with a field to sort on.
  564. # The ispell/awk combination is similar to the ispell/sed pipe used to
  565. # generate EXPANDEDPAIRS, except that ispell adds an extra field
  566. # giving the sort order.  The first sort gets things in order so the
  567. # first root listed is the one we want, and the second sort (-um) then
  568. # selects that first root.  Sed strips the expansion from the root,
  569. # and a final sort -u generates MINIMALAFFIXES, the final list of
  570. # affixes that (more or less) minimally covers what it can from
  571. # EXPANDEDINPUT.
  572. $verbose  &&  echo 'Eliminating non-optimal affixes.' 1>&2
  573. ispell "$wchars" -e4 -d $FAKEHASH -p /dev/null < $LEGALFLAGLIST \
  574.   | sort $SORTTMP +1 -2 +2rn -3 +0 -1 \
  575.   | sort $SORTTMP -um +1 -2 \
  576.   | sed -e 's; .*$;;' \
  577.   | sort $SORTTMP -u "-t$flagmarker" +0f -1 +0 > $MINIMALAFFIXES
  578. /bin/rm -f $LEGALFLAGLIST
  579. # Now we're almost done.  MINIMALAFFIXES covers some (with luck, most)
  580. # of the words in STRIPPEDINPUT.  Now we must create a list of the remaining
  581. # words (those omitted by MINIMALAFFIXES) and add it to MINIMALAFFIXES.
  582. # The best way to do this is to actually build a partial dictionary from
  583. # MINIMALAFFIXES in FAKEHASH, and then use ispell -l to list the words that
  584. # are not covered by this dictionary.  This must then be combined with the
  585. # reduced version of MINIMALAFFIXES and sorted to produce the final result.
  586. $verbose  &&  echo "Generating output word list." 1>&2
  587. if [ -s $MINIMALAFFIXES ]
  588.     buildhash -s $MINIMALAFFIXES $langtabs $FAKEHASH > /dev/null \
  589.       ||  (echo "Couldn't create intermediate hash file" 1>&2;
  590.     /bin/rm -f ${TMP}*;
  591.     exit 1) \
  592.       ||  exit 1
  593.     if [ "$debug" = yes ]
  594.     then
  595.     rm -f ${TDIR}/MINAFFIXES..cnt \
  596.       ${TDIR}/MINAFFIXES.stat
  597.     ln $MINIMALAFFIXES..cnt ${TDIR}/MINAFFIXES..cnt
  598.     ln $MINIMALAFFIXES.stat ${TDIR}/MINAFFIXES.stat
  599.     fi
  600.     (ispell "$wchars" -l -d $FAKEHASH -p /dev/null < $STRIPPEDINPUT; \
  601.     $COMBINE $langtabs < $MINIMALAFFIXES) \
  602.       | sort $SORTTMP "-t$flagmarker" -u +0f -1 +0
  603.     # MINIMALAFFIXES is empty;  just produce a sorted version of STRIPPEDINPUT
  604.     sort $SORTTMP "-t$flagmarker" -u +0f -1 +0 $STRIPPEDINPUT
  605. /bin/rm -f ${TMP}*
  606. if [ "X$MUNCHMAIL" != X ]
  607.     (
  608.     ls -ld ${TDIR}/[A-Z]*
  609.     cat /tmp/munchlist.mail
  610.     ) | mail "$MUNCHMAIL"
  611.     /bin/rm -f /tmp/munchlist.mail
  612. exit 0
  613.